#ifndef WLED_PIN_MANAGER_H
#define WLED_PIN_MANAGER_H
/*
 * Registers pins so there is no attempt for two interfaces to use the same pin
 */
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP32
#include "driver/ledc.h" // needed for analog/LEDC channel counts
#endif
#include "const.h" // for USERMOD_* values

typedef struct PinManagerPinType {
  int8_t pin;
  bool   isOutput;
} managed_pin_type;

/*
 * Allows PinManager to "lock" an allocation to a specific
 * owner, so someone else doesn't accidentally de-allocate
 * a pin it hasn't allocated.  Also enhances debugging.
 *
 * RAM Cost:
 *     17 bytes on ESP8266
 *     40 bytes on ESP32
 */
enum struct PinOwner : uint8_t {
  None          = 0,      // default == legacy == unspecified owner
  // High bit is set for all built-in pin owners
  Ethernet      = 0x81,
  BusDigital    = 0x82,
  BusOnOff      = 0x83,
  BusPwm        = 0x84,   // 'BusP' == PWM output using BusPwm
  Button        = 0x85,   // 'Butn' == button from configuration
  IR            = 0x86,   // 'IR'   == IR receiver pin from configuration
  Relay         = 0x87,   // 'Rly'  == Relay pin from configuration
  SPI_RAM       = 0x88,   // 'SpiR' == SPI RAM
  DebugOut      = 0x89,   // 'Dbg'  == debug output always IO1
  DMX           = 0x8A,   // 'DMX'  == hard-coded to IO2
  HW_I2C        = 0x8B,   // 'I2C'  == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
  HW_SPI        = 0x8C,   // 'SPI'  == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
  // Use UserMod IDs from const.h here
  UM_Unspecified       = USERMOD_ID_UNSPECIFIED,        // 0x01
  UM_Example           = USERMOD_ID_EXAMPLE,            // 0x02 // Usermod "usermod_v2_example.h"
  UM_Temperature       = USERMOD_ID_TEMPERATURE,        // 0x03 // Usermod "usermod_temperature.h"
  // #define USERMOD_ID_FIXNETSERVICES                  // 0x04 // Usermod "usermod_Fix_unreachable_netservices.h" -- Does not allocate pins
  UM_PIR               = USERMOD_ID_PIRSWITCH,          // 0x05 // Usermod "usermod_PIR_sensor_switch.h"
  UM_IMU               = USERMOD_ID_IMU,                // 0x06 // Usermod "usermod_mpu6050_imu.h" -- Interrupt pin
  UM_FourLineDisplay   = USERMOD_ID_FOUR_LINE_DISP,     // 0x07 // Usermod "usermod_v2_four_line_display.h -- May use "standard" HW_I2C pins
  UM_RotaryEncoderUI   = USERMOD_ID_ROTARY_ENC_UI,      // 0x08 // Usermod "usermod_v2_rotary_encoder_ui.h"
  // #define USERMOD_ID_AUTO_SAVE                       // 0x09 // Usermod "usermod_v2_auto_save.h" -- Does not allocate pins
  // #define USERMOD_ID_DHT                             // 0x0A // Usermod "usermod_dht.h" -- Statically allocates pins, not compatible with pinManager?
  // #define USERMOD_ID_VL53L0X                         // 0x0C // Usermod "usermod_vl53l0x_gestures.h" -- Uses "standard" HW_I2C pins
  UM_MultiRelay        = USERMOD_ID_MULTI_RELAY,        // 0x0D // Usermod "usermod_multi_relay.h"
  UM_AnimatedStaircase = USERMOD_ID_ANIMATED_STAIRCASE, // 0x0E // Usermod "Animated_Staircase.h"
  UM_Battery           = USERMOD_ID_BATTERY,            //
  // #define USERMOD_ID_RTC                             // 0x0F // Usermod "usermod_rtc.h" -- Uses "standard" HW_I2C pins
  // #define USERMOD_ID_ELEKSTUBE_IPS                   // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h
  // #define USERMOD_ID_SN_PHOTORESISTOR                // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager
  UM_BH1750            = USERMOD_ID_BH1750,             // 0x14 // Usermod "bh1750.h -- Uses "standard" HW_I2C pins
  UM_RGBRotaryEncoder  = USERMOD_RGB_ROTARY_ENCODER,    // 0x16 // Usermod "rgb-rotary-encoder.h"
  UM_QuinLEDAnPenta    = USERMOD_ID_QUINLED_AN_PENTA,   // 0x17 // Usermod "quinled-an-penta.h"
  UM_BME280            = USERMOD_ID_BME280,             // 0x1E // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins
  UM_Audioreactive     = USERMOD_ID_AUDIOREACTIVE,      // 0x20 // Usermod "audio_reactive.h"
  UM_SdCard            = USERMOD_ID_SD_CARD,            // 0x25 // Usermod "usermod_sd_card.h"
  UM_PWM_OUTPUTS       = USERMOD_ID_PWM_OUTPUTS,        // 0x26 // Usermod "usermod_pwm_outputs.h"
  UM_LDR_DUSK_DAWN     = USERMOD_ID_LDR_DUSK_DAWN,      // 0x2B // Usermod "usermod_LDR_Dusk_Dawn_v2.h"
  UM_MAX17048          = USERMOD_ID_MAX17048,           // 0x2F // Usermod "usermod_max17048.h"
  UM_BME68X            = USERMOD_ID_BME68X,             // 0x31 // Usermod "usermod_bme68x.h -- Uses "standard" HW_I2C pins
  UM_PIXELS_DICE_TRAY  = USERMOD_ID_PIXELS_DICE_TRAY    // 0x35 // Usermod "pixels_dice_tray.h" -- Needs compile time specified 6 pins for display including SPI.
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

class PinManager {
  private:
    #ifdef ESP8266
    #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17)
    static uint32_t pinAlloc;     // 1 bit per pin, we use first 17bits
    #else
    #define WLED_NUM_PINS (GPIO_PIN_COUNT)
    static uint64_t pinAlloc;     // 1 bit per pin, we use 50 bits on ESP32-S3
    static uint16_t ledcAlloc;    // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS)
    #endif
    static uint8_t i2cAllocCount; // allow multiple allocation of I2C bus pins but keep track of allocations
    static uint8_t spiAllocCount; // allow multiple allocation of SPI bus pins but keep track of allocations
    static PinOwner ownerTag[WLED_NUM_PINS];

  public:
    // De-allocates a single pin
    static bool deallocatePin(byte gpio, PinOwner tag);
    // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified)
    static bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag);
    static bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag);
    // Allocates a single pin, with an owner tag.
    // De-allocation requires the same owner tag (or override)
    static bool allocatePin(byte gpio, bool output, PinOwner tag);
    // Allocates all the pins, or allocates none of the pins, with owner tag.
    // Provided to simplify error condition handling in clients
    // using more than one pin, such as I2C, SPI, rotary encoders,
    // ethernet, etc..
    static bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag );

    [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]]
    static inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); }
    [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]]
    static inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); }

    // will return true for reserved pins
    static bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None);
    // will return false for reserved pins
    static bool isPinOk(byte gpio, bool output = true);
    
    static bool isReadOnlyPin(byte gpio);

    static PinOwner getPinOwner(byte gpio);

    #ifdef ARDUINO_ARCH_ESP32
    static byte allocateLedc(byte channels);
    static void deallocateLedc(byte pos, byte channels);
    #endif
};

//extern PinManager pinManager;
#endif
